package orbit

import (
	"strings"

	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/binocle"
	"gddgame.cc/galaxy/binocle/logic"
	"gddgame.cc/galaxy/binocle/module/socket"
	"gddgame.cc/galaxy/common/feature/orbit"
	"gddgame.cc/galaxy/common/feature/orbit/sync_protocol"
	"gddgame.cc/galaxy/project/orbit/orbit/sync"
	"gddgame.cc/galaxy/satellite"
	"gddgame.cc/galaxy/satellite/formula/comet"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/server/web"
	"gddgame.cc/galaxy/utils"
)

type syncChannel struct {
	builder composer.Builder
	cloud   logic.Cloud
	single  *sync.SingleModeService
	area    *sync.AreaModeService
	cluster *sync.ClusterModeService
}

func (channel *syncChannel) Connect(socket *comet.Socket) error {
	socket.SetCompress(false)
	socket.SetTransport(satellite.SpeedTransport)

	// 全局设置
	c := sync.NewClient()
	c.Bind(socket)
	if err := socket.Set("client", c); err != nil {
		return err
	}

	socket.Disconnected(func(reason string) {
		// 移除所有
		c.Reset(false)
		c.Release()
	})

	socket.UpdateAuth(func(authority interface{}) {
		a := authority.(*auth)
		container := channel.cloud.ClusterWith(a)

		c.Region = a.Region()
		if c.OpenId != a.openId {
			c.OpenId = a.openId
		}
		syncNS := a.nsMap["sync"]
		if syncNS == nil {
			syncNS = &ns{settings: map[string]interface{}{}}
		}
		config := &sync_protocol.AreaConfig{
			Area:           "",
			SyncMode:       sync_protocol.SingleSyncMode,
			MaxVision:      0,
			Gap:            0,
			Rate:           100,
			UpdateLocation: false,
		}
		if _, ok := syncNS.settings["rate"]; ok {
			config.Rate = utils.ToInt32(syncNS.settings["rate"])
		}
		if _, ok := syncNS.settings["gap"]; ok {
			config.Gap = utils.ToInt32(syncNS.settings["gap"])
		}
		if _, ok := syncNS.settings["vision"]; ok {
			config.MaxVision = utils.ToInt32(syncNS.settings["vision"])
		}
		if _, ok := syncNS.settings["update"]; ok {
			config.UpdateLocation = utils.ToBool(syncNS.settings["update"])
		}
		if _, ok := syncNS.settings["mode"]; ok {
			config.SyncMode = sync_protocol.SyncMode(utils.ToByte(syncNS.settings["mode"]))
		}
		if _, ok := syncNS.settings["config"]; ok {
			_, _ = config.UnmarshalMsg(utils.ToBytes(syncNS.settings["config"]))
		}
		c.Manager = channel

		c.UpdateConfig(config, container)

		socket.Dispatch(func(event comet.DE, packet comet.Packet) {
			//fmt.Println("block", event)\
			c.Dispatch(container, event, packet)
		})
	})

	return nil
}

func (channel *syncChannel) Disconnect(socket *comet.Socket, reason string) error {
	return nil
}

func (channel *syncChannel) SwitchMode(mode sync_protocol.SyncMode) sync.ModeService {
	switch mode {
	case sync_protocol.ClusterSyncMode:
		return channel.cluster
	case sync_protocol.AreaSyncMode:
		return channel.area
	case sync_protocol.SingleSyncMode:
		fallthrough
	default:
		return channel.single
	}
}

/*
 * 数据同步逻辑
 */
func SyncHandler(builder composer.Builder, config *Config, env *orbit.Environment) comet.Interface {
	var agent binocle.Service
	builder.Factory("agent", &agent)

	blockService := &sync.BlockService{
		Logger:   builder,
		Service:  agent,
		MaxDelay: 200, // 200ms延迟以外为不可用
		MinDelay: 100, // 100ms以内为高可用
		AvailGap: 66,  // 66%作为最低可用筏值
	}
	blockService.GetSocketClient = func(socket *comet.Socket) *sync.Client {
		var result *sync.Client
		err := socket.Get("client", &result)
		if err == nil {
			return result
		} else {
			return nil
		}
	}

	clusterMode := &sync.ClusterModeService{
		Logger:  builder,
		Service: agent,
		Block:   blockService,
	}
	agent.Cluster().RegisterBalance(clusterMode)

	areaMode := &sync.AreaModeService{
		Logger:  builder,
		Service: agent,
		Block:   blockService,
	}
	areaMode.Pool()
	agent.Cluster().RegisterBalance(areaMode)

	singleMode := &sync.SingleModeService{
		Logger:  builder,
		Service: agent,
		Block:   blockService,
	}
	agent.Cluster().RegisterBalance(singleMode)

	c := &syncChannel{
		builder: builder,
		cloud:   agent,
		single:  singleMode,
		area:    areaMode,
		cluster: clusterMode,
	}
	agent.ListenStream("sync/single/data", func(request socket.StreamRequest) bool {
		// 同步data数据到游戏服务器
		hit := false
		var areaId string
		_ = request.Data(&areaId)
		agent.ClusterWith(request).Balance().ServiceRange(singleMode.ID(), func(node cluster.Node, master bool) bool {
			// block master 包含areaId
			if !master || !strings.Contains(node.Token(), areaId) {
				return true
			}
			hit = true
			singleMode.HandleStreamData(request, node.Instance())
			return false
		})
		return hit
	})
	agent.ListenDo("sync/area/create", func(request socket.Request) {
		// 创建副本
		container := agent.ClusterWith(request)
		var areaCreate sync_protocol.AreaCreateRequest
		if err := request.Data(&areaCreate); err != nil {
			return
		}
		token := container.Balance().CreateService(areaMode.ID())
		client := container.BalanceClient(areaMode.ID(), token)
		client.Emit().Data(request.Serialize()).Send(sync.SettingAreaEvent)
		request.Response(token)
	})
	agent.ListenDo("sync/area/close", func(request socket.Request) {
		// 关闭区块
		container := agent.ClusterWith(request)
		var areaClose sync_protocol.AreaCloseRequest
		if err := request.Data(&areaClose); err != nil {
			return
		}
		client := container.BalanceClient(areaMode.ID(), areaClose.Token)
		client.Emit().Send(cluster.CloseEvent)
	})
	agent.ListenDo("sync/cluster/init", func(request socket.Request) {
		// 初始化

	})
	agent.ListenDo("sync/cluster/item", func(request socket.Request) {
		// 添加信息
		container := agent.ClusterWith(request)
		var addItem sync_protocol.AddItemRequest
		if err := request.Data(&addItem); err != nil {
			return
		}
		token := clusterMode.GetNodeId(addItem.Area, &addItem.Block)
		client := container.BalanceClient(clusterMode.ID(), token)
		for _, item := range addItem.Items {
			client.Emit().Data(item).Send(sync_protocol.Instance)
		}
	})
	agent.ListenDo("sync/cluster/time", func(request socket.Request) {
		container := agent.ClusterWith(request)
		var addTime sync_protocol.AddTimeRequest
		if err := request.Data(&addTime); err != nil {
			return
		}
		token := clusterMode.GetNodeId(addTime.Area, &addTime.Block)
		client := container.BalanceClient(clusterMode.ID(), token)
		for _, time := range addTime.Times {
			client.Emit().Data(time).Send(sync_protocol.Instance)
		}
	})
	return c
}

func SyncAppHandler(builder composer.Builder, router web.Router, config *Config) {
	var agent binocle.Service
	builder.Factory("agent", &agent)
	agent.MountNode(func(node cluster.Node) {

	})
}

func SyncManageHandler(builder composer.Builder, router web.Router, config *Config) {
	var agent binocle.Service
	builder.Factory("agent", &agent)
	agent.AddMetrics().Exec("sync.block", func(key string) float32 {
		return float32(sync.BlockNumber)
	})
}
